<?php


namespace App\Utils;


use App\Constants\Business;
use App\Constants\ErrorCode;
use App\Constants\RedisKey;
use App\Model\User;
use App\Service\Contract\IUserAuthService;
use Hyperf\HttpServer\Contract\ResponseInterface;


/**
 * 助手类 提供一些助手函数 静态方式调用
 * Class Helper
 * @package App\Common
 */
class Helper
{
    /**
     * Notes: 响应json
     * User: 陈朋
     * DateTime: 2021/6/15 18:07
     * @param int|array $errorCode
     * @param mixed $data
     * @return mixed
     */
    public static function outputJson($errorCode, $data = [])
    {
        if (!is_array($errorCode)) {
            $errorCode = [$errorCode];
        }
        return response()->json([
            'code' => $errorCode[0],
            'msg' => ErrorCode::getMessage(...$errorCode),
            'data' => $data
        ]);
    }

    /**
     * Notes: 数组存redis Hash
     * User: 陈朋
     * DateTime: 2021/7/2 15:15
     * @param array $arr
     * @param string $hash_key
     * @param string $db
     * @param int $ttl
     */
    public static function arrToHashInRedis(array $arr, string $hash_key, string $db = 'default', int $ttl = RedisKey::EXPIRE_2H): void
    {
        $data = [];
        foreach ($arr as $key => $val) {
            $data[(string)$key] = json_encode($val);
        }
        $redis = redis($db);
        $redis->hMSet($hash_key, $data);
        if ($ttl) {
            $redis->expire($hash_key, $ttl);
        }
    }

    /**
     * Notes: redis中获取hash类型 返回格式化后的数组
     * User: 陈朋
     * DateTime: 2021/8/2 14:28
     * @param string $hash_key
     * @param string $db
     * @return array
     */
    public static function hashToArrFromRedis(string $hash_key, string $db = 'default'): array
    {
        $data = redis($db)->hGetAll($hash_key);

        foreach ($data as &$value) {
            $decode = json_decode($value, true);
            if (!is_null($decode)) {
                $value = $decode;
            }
        }
        return $data;
    }

    /**
     * Notes: 生成随机用户名 注册时使用 后期可改 理论上最好不重复
     * User: 陈朋
     * DateTime: 2021/7/3 15:28
     * @param string $lang
     * @param int $gender 性别
     * @return string
     */
    public static function generateNickname(string $lang = Business::LANG_PORTUGAL, int $gender = User::GENDER_FEMALE): string
    {
        //避免同进程随机种子相同 每次重置种子
        mt_rand();
        if (!in_array($lang, [Business::LANG_PORTUGAL, Business::LANG_ENGLISH])) {
            $lang = Business::LANG_PORTUGAL;
        }
        //现在只生成葡萄牙语的名子
        $lang = Business::LANG_PORTUGAL;

        $family_name_file = 'family_names.json';
        $name_file = 'name.json';

        $file_path = BASE_PATH . "/storage/nickname/$lang";

        //$family_names = file_get_contents($file_path . "/$family_name_file");
        $names = file_get_contents($file_path . "/$name_file");

        //$family_names = json_decode($family_names, true);
        $names = json_decode($names, true);

        $name_arr = [];
        switch ($gender) {
            case User::GENDER_MALE:
                $name_arr = $names['male'];
                break;
            case User::GENDER_FEMALE:
                $name_arr = $names['female'];
                break;
        }

        if (!$name_arr) {
            return 'guest_' . self::randomString(6, 'number');
        }

        return $name_arr[array_rand($name_arr)];
    }

    /**
     * Notes: 格式化游戏回放日志
     * User: 陈朋
     * DateTime: 2021/6/25 17:09
     * @param array $log
     * @return array
     */
    public static function formatLog(array $log): array
    {
        foreach ($log as $step => &$step_data) {
            $step_data = json_decode($step_data, true);
            if ($step_data['action'] == 'GameStartEvent') {
                continue;
            }
            $pre_step_data = $log[$step - 1]['data'];

            if (isset($step_data['data']['game_progress'])) {
                $step_data['data']['game_progress'] = array_merge($pre_step_data['game_progress'], $step_data['data']['game_progress']);
            }
            $step_data['data'] = array_merge($pre_step_data, $step_data['data']);
        }
        return $log;
    }

    /**
     * Notes: 随机字符串
     * User: 陈朋
     * DateTime: 2021/7/2 13:37
     * @param int $length 长度
     * @param string $type 类型 number: 纯数字 | letter: 大小写字母 | string: 大小写字母数字 不包括 i l o 0 | all: 所有大小写字母数字
     * @param bool $convert 转换大小写 true 大写 false 小写
     * @return string
     */
    public static function randomString(int $length = 10, string $type = 'all', bool $convert = false): string
    {
        mt_srand();
        $config = [
            'number' => '1234567890',
            'letter' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
            'string' => 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789',
            'all' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
        ];
        if (!isset($config[$type])) $type = 'letter';
        $string = $config[$type];
        $code = '';
        $str_len = strlen($string) - 1;
        for ($i = 0; $i < $length; $i++) {
            $code .= $string[mt_rand(0, $str_len)];
        }
        return $convert ? strtoupper($code) : strtolower($code);
    }

    /**
     * Notes: 获取包名
     * User: 陈朋
     * DateTime: 2021/7/2 15:03
     * @return string
     */
    public static function getPackageName(): string
    {
        return getHeader(Business::PACKAGE_NAME);
    }

    /**
     * Notes: 获取客户端版本号
     * User: 陈朋
     * DateTime: 2021/7/2 15:03
     * @return string
     */
    public static function getVersion(): string
    {
        return getVersion();
    }

    /**
     * Notes: 游戏内数据组建
     * User: 陈朋
     * DateTime: 2021/7/7 13:08
     * @param $errorCode
     * @param array $data
     * @return array
     */
    public static function gameDataFormat($errorCode, array $data = []): array
    {
        return self::wsDataFormat($errorCode, $data, \App\Games\Canastra\Conf\ErrorCode::class);
    }

    /**
     * Notes: 长连接数据返回格式化
     * User: 陈朋
     * DateTime: 2021/7/7 9:27
     * @param $errorCode
     * @param array $data
     * @return array
     */
    public static function wsDataFormat($errorCode, array $data = [], $errorClass = ErrorCode::class): array
    {
        if (!is_array($errorCode)) {
            $errorCode = [$errorCode, ''];
        }
        $msg_data = [
            'status' => $errorCode[0],
            'msg' => $errorClass::getMessage(...$errorCode),
        ];
        return array_merge($msg_data, $data);
    }

    /**
     * Notes: 获取当前毫秒
     * User: 陈朋
     * DateTime: 2021/7/8 11:33
     * @return float
     */
    public static function getMillisecond(): float
    {
        [$t1, $t2] = explode(' ', microtime());
        return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
    }

    /**
     * Notes: 获取dao层命名空间
     * User: 陈朋
     * DateTime: 2021/7/9 23:19
     * @return string
     */
    public static function getModelNameSpace(): string
    {
        return "App\\Model\\";
    }

    /**
     * Notes: 格式化道具参数
     * User: 陈朋
     * DateTime: 2021/7/19 18:40
     * @param array $prop_require
     * @param bool $add
     * @return array
     */
    public static function formatProps(array $prop_require, bool $add = true): array
    {
        $props = [];
        $negative = $add ? 1 : -1;
        foreach ($prop_require as $prop_id => $count) {
            //数量为0 那就跳过这个
            if (!$count) {
                continue;
            }
            $props[] = ['propId' => $prop_id, 'propNum' => $count * $negative];
        }
        return $props;
    }

    /**
     *  将形如  {$propId}@{$propNum}|{$propId}@{$propNum}  转成
     *  [
     *      ['propId' => $propId,'propNum'=>$propNum],
     *      ['propId' => $propId,'propNum'=>$propNum],
     *  ]
     * User: caijw
     * DateTime: 2021/11/24 16:28
     * @param string $props
     * @param bool $add
     * @return array
     */
    public static function formatPropsFromStr(string $props, bool $add = true): array
    {
        if (empty($props)) {
            return [];
        }
        $map = [];
        foreach (explode("|", $props) as $value) {
            [$propId, $propNum] = explode('@', $value);
            if (empty($map[$propId])) {
                $map[$propId] = 0;
            }
            $map[$propId] += $propNum;
        }
        return self::formatProps($map, $add);
    }

    /**
     * Notes: 给定两个数字，获取闭合区间
     * User: 陈朋
     * DateTime: 2021/7/23 16:45
     * @param int $start
     * @param int $end
     * @return array
     */
    public static function getContinueSection(int $start, int $end): array
    {
        if ($start === $end) {
            return [$start];
        }
        if ($start > $end) {
            [$end, $start] = [$start, $end];
        }
        $section = [];
        while ($start <= $end) {
            $section[] = $start;
            $start++;
        }
        return $section;
    }

    /**
     * Notes: 获取加权随机数
     * User: 陈朋
     * DateTime: 2021/7/24 14:55
     * @param array $keys_weight [key => weight, key => weight]
     * @return string return one of the key in $keys_weight
     */
    public static function getWeightRandom(array $keys_weight): string
    {
        $sum = 0;
        $keys_weight_point = array(0);//这个数组记录了每个切割点的值，就是记录了数轴上，5,15,30,50,100的值。
        foreach ($keys_weight as $value) {
            $sum += $value;//计算出权值的总和
            array_push($keys_weight_point, $sum);//把分割点放到数组中
        }
        $num = rand(0, $sum);//取0到sum之间一个随机值
        for ($i = 0; $i < count($keys_weight_point) - 1; $i++) {
            if ($num >= $keys_weight_point[$i] && $num <= $keys_weight_point[$i + 1]) //判断随机值落在哪个范围内
            {
                $elem = array_slice($keys_weight, $i, 1);
                return (string)array_search($elem[0], $keys_weight);
            }
        }
        //不然就返回第一个key
        $keys = array_keys($keys_weight);
        return (string)reset($keys);
    }

    /**
     * Notes: 获取明天开始的时间戳 明天 00：00：00
     * User: 陈朋
     * DateTime: 2021/7/26 14:07
     * @return int
     */
    public static function getNextDayStartTimestamp(): int
    {
        return strtotime(date('Y-m-d', strtotime('+1 day')));
    }

    /**
     * Notes: 从url中解析出query参数
     * User: 陈朋
     * DateTime: 2021/8/4 10:32
     * @param string $url
     * @return array
     */
    public static function parseQuery(string $url): array
    {
        $query = parse_url($url);
        if (!isset($query['query'])) {
            return [];
        }
        $params = explode('&', $query['query']);
        $k_v = [];
        foreach ($params as $param) {
            [$key, $value] = explode('=', $param);
            $k_v[$key] = $value;
        }
        return $k_v;
    }

    /**
     * Notes: 格式化星星
     * User: 陈朋
     * DateTime: 2021/9/8 14:23
     * @param array $get_stars
     * @return array
     */
    public static function formatStars(array $get_stars): array
    {
        $stars = [
            'star0',
            'star1',
            'star2',
        ];
        $ret = [];
        foreach ($stars as $name) {
            if (isset($get_stars[$name])) {
                $ret[] = 1;
            } else {
                $ret[] = 0;
            }
        }
        return $ret;
    }

    /**
     * Notes: 格式化 @ | 数据   30006@1|30002@1 =>  [30006=>1, 30002=>1]
     * 2021-11-23 姚磊 修改 增加参数 格式化样式$format_type 默认是 false，如果开启返回结构 [['propId' => 30006, 'propNum' => 1]];
     * User: 陈朋
     * DateTime: 2021/10/20 15:15
     * @param string $data_string
     * @param bool $value_force_int value强转int类型
     * @param bool $format_type
     * @return array
     */
    public static function formatAtDashData(string $data_string, bool $value_force_int = true, bool $format_type = false): array
    {
        $data_arr = explode('|', $data_string);
        $ret = [];
        foreach ($data_arr as $item) {
            if ($format_type) {
                [$getPropId, $getPropNum] = explode('@', $item);
                $getPropNum = $value_force_int ? (int)$getPropNum : $getPropNum;
                $ret[] = ['propId' => (int)$getPropId, 'propNum' => $getPropNum];
            } else {
                [$key, $value] = explode('@', $item);
                $ret[$key] = $value_force_int ? (int)$value : $value;
            }
        }
        return $ret;
    }

    /**
     * Notes: 标准化性别
     * User: 陈朋
     * DateTime: 2021/12/9 18:38
     * @param string $gender
     * @return int
     */
    public static function formatGender(string $gender): int
    {
        switch ($gender) {
            case 'female':
                $gender = User::GENDER_FEMALE;
                break;
            case 'male':
                $gender = User::GENDER_MALE;
                break;
            default:
                $gender = User::GENDER_UNDEFINED;
                break;
        }
        return $gender;
    }

    /**
     * Notes:十进制转二进制、八进制、十六进制 不足位数前面补零
     * User: YaoLei
     * DateTime: 2021/11/25
     * @param array $datalist 传入数据array(100,123,130)
     * @param int $bin 转换的进制可以是：2,8,16
     * @return array|mixed 返回数据 array() 返回没有数据转换的格式
     */
    function decto_bin(array $datalist, int $bin): array
    {
        static $arr = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F');
        if (!is_array($datalist)) $datalist = array($datalist);
        if ($bin == 10) return $datalist; //相同进制忽略
        $bytelen = ceil(16 / $bin); //获得如果是$bin进制，一个字节的长度
        $aOutChar = array();
        foreach ($datalist as $num) {
            $t = "";
            $num = intval($num);
            if ($num === 0) continue;
            while ($num > 0) {
                $t = $arr[$num % $bin] . $t;
                $num = floor($num / $bin);
            }
            $tlen = strlen($t);
            if ($tlen % $bytelen != 0) {
                $pad_len = $bytelen - $tlen % $bytelen;
                $t = str_pad("", $pad_len, "0", STR_PAD_LEFT) . $t; //不足一个字节长度，自动前面补充0
            }
            $aOutChar[] = $t;
        }
        return $aOutChar;
    }

    /**
     * Notes:二进制、八进制、十六进制 转十进制*
     * User: YaoLei
     * DateTime: 2021/11/25
     * @param array $datalist 传入数据array(df,ef)
     * @param int $bin 转换的进制可以是：2,8,16
     * @return array|mixed 返回数据 array() 返回没有数据转换的格式
     */
    function bin_todec(array $datalist, int $bin): array
    {
        static $arr = array('0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15);
        if (!is_array($datalist)) $datalist = array($datalist);
        if ($bin == 10) return $datalist; //为10进制不转换
        $aOutData = array(); //定义输出保存数组
        foreach ($datalist as $num) {
            $atnum = str_split($num); //将字符串分割为单个字符数组
            $atlen = count($atnum);
            $total = 0;
            $i = 1;
            foreach ($atnum as $tv) {
                $tv = strtoupper($tv);
                if (array_key_exists($tv, $arr)) {
                    if ($arr[$tv] == 0) continue;
                    $total = $total + $arr[$tv] * pow($bin, $atlen - $i);
                }
                $i++;
            }
            $aOutData[] = $total;
        }
        return $aOutData;
    }

    /**
     * Notes: 获取参数的签名
     * User: Ken
     * DateTime: 2024/9/4
     * @param array $param
     * @return string
     */
    public static function getParamsSign(array $param): string
    {
        if (empty($param)) return '';
        ksort($param);
        $queryString = urldecode(http_build_query($param));
        $secret = env('SIGN_SECRET', 'Dream');
        return md5($secret . $queryString);
    }

    /**
     * Notes: 数据返回
     * User: Ken
     * DateTime: 2024/10/16
     * @param int $code
     * @param string $msg
     * @param array $data
     * @return ResponseInterface
     */
    private static function respond(int $code, string $msg, array $data)
    {
        $response = [
            'code' => $code,
            'server_time' => Helper::getMillisecond(),
            'msg' => $msg,
        ];
        // 去掉无数据时的空data值 解决客户端解析有数据和无数据时的返回的格式不统一问题
        //if ($code != ErrorCode::SUCCESS_NO_DATA){
        if (!empty($data)){
            $response['data'] = $data;
        }
        return response()->json($response);
    }

    /**
     * Notes: 错误数据返回
     * User: Ken
     * DateTime: 2024/10/16
     * @param int $code
     * @param string $msg
     * @param array $data
     * @return ResponseInterface
     */
    public static function respondError(int $code, string $msg = '', array $data = [])
    {
        return self::respond($code, $msg, $data);
    }

    /**
     * Notes: 正确数据返回
     * User: Ken
     * DateTime: 2024/10/16
     * @param array $data
     * @return ResponseInterface
     */
    public static function respondSuccess(array $data)
    {
        return self::respond(ErrorCode::SUCCESS, ErrorCode::getMessage(ErrorCode::SUCCESS), $data);
    }
}
